
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Threading;
using System.Xml;
using Swaf.BizObj;
using Swaf.BizRule;

namespace Swaf.Call
{
    /// <summary>
    /// Base class for rules that contain other rules.
    /// </summary>
    public abstract class BaseCompositeCall : BaseCall
    {
        static readonly int FAILED_CALLS_BEFORE_DISABLE = 3;
        static readonly int MINUTES_BEFORE_RE_ENABLE = 5;

        protected IList m_internalCalls = new ArrayList();
        protected IDictionary m_internalCallKeys = new Hashtable();
        protected string m_dynamicLoadRule = null;

        public BaseCompositeCall(string id, XmlElement initInfo)
            : base(id, initInfo)
        {
            //If a dynamicLoadRule attribute is provided it must reference a rule that
            // returns an IBizObjList of CompositeCallWorker bizobjs.  This rule will get
            // called once the composite rule is called for the first time.
            string dynamicRule = initInfo.GetAttribute("dynamicLoadRule");
            if (!dynamicRule.Equals(string.Empty))
                m_dynamicLoadRule = dynamicRule;

            int numFailBeforeDisable = GetAttrValue(initInfo, "numFailBeforeDisable", "$NumFailCallsBeforeDisable$", FAILED_CALLS_BEFORE_DISABLE);
            int numMinBeforeReEnable = GetAttrValue(initInfo, "numMinBeforeReEnable", "$NumMinBeforeReEnable$", MINUTES_BEFORE_RE_ENABLE);

            IClassFactory m_callFactory = Application.currentApp.factories["CallTypes"];
            foreach (XmlNode n in initInfo.ChildNodes)
            {
                if (n is XmlElement)
                    m_internalCalls.Add(new internalCallMember((ICall)m_callFactory.createFromNickName(n.Name, id, n), Id, numFailBeforeDisable, numMinBeforeReEnable));
            }
        }

        protected int GetAttrValue(XmlElement initInfo, string attrName, string macroName, int defaultValue)
        {
            int attrVal = defaultValue;

            string attrValStr = initInfo.GetAttribute(attrName);
            if (attrValStr != null && attrValStr != string.Empty)
            {
                try
                {
                    attrVal = Convert.ToInt32(attrValStr);
                    return attrVal;
                }
                catch
                {
                    // Don't let error bubble up, just try the macro value
                }
            }

            attrValStr = Application.currentApp.resMgr.resolve(macroName);
            if (attrValStr != null && attrValStr != string.Empty && attrValStr[0] != '$')
            {
                try
                {
                    attrVal = Convert.ToInt32(attrValStr);
                    return attrVal;
                }
                catch
                {
                    // Don't let error bubble up, just use the default value
                }
            }

            return attrVal;
        }

        protected void loadInternalListIfEmpty()
        {
            if (m_internalCalls.Count == 0 && m_dynamicLoadRule != null)
            {
                lock (this)
                {
                    // Recheck internalCalls count incase we got beat to initialization
                    if (m_internalCalls.Count == 0)
                    {
                        IClassFactory m_callFactory = Application.currentApp.factories["CallTypes"];
                        IBizObjList list = (IBizObjList)Application.currentApp.ruleMgr.execute(m_dynamicLoadRule, null);
                        foreach (IBizObj rule in list)
                        {
                            string callType = (string)rule.get("callType");
                            ICall call = (ICall)m_callFactory.createFromNickName(callType, rule);
                            addCall(call);
                        }
                    }
                }
            }
        }
        public void addCall(ICall newCall)
        {
            try
            {
                lock (m_internalCalls)
                {
                    internalCallMember newMem = new internalCallMember(newCall, Id, FAILED_CALLS_BEFORE_DISABLE, MINUTES_BEFORE_RE_ENABLE);
                    m_internalCalls.Add(newMem);
                    m_internalCallKeys.Add(newMem.id, m_internalCalls.IndexOf(newMem));
                }
            }
            catch (Exception e)
            {
                throw;
            }
        }
        /*
                public void removeCall(ICall callObj)
                {
                    try
                    {
                        m_accessLock.AcquireWriterLock(-1);
                        m_internalCalls.Remove(callObj);
                    }
                    finally
                    {
                        m_accessLock.ReleaseWriterLock();
                    }
                }
        */
        public void removeCall(Swaf.BGuid.Guid id)
        {
            try
            {
                lock (m_internalCalls)
                {
                    int idx = (int)m_internalCallKeys[id];
                    m_internalCalls.RemoveAt(idx);
                }
            }
            catch (Exception e)
            {
                throw;
            }
        }
        public IBizObjList getWorkerList()
        {
            try
            {
                IBizObjList lst = null;
                lock (m_internalCalls)
                {
                    lst = new Swaf.BizObj.basic.FullBizObjList("Workers", Application.currentApp.bizObjMgr.getDefinition("CompositeCallWorker"));
                    foreach (internalCallMember c in m_internalCalls)
                    {
                        IBizObj w = lst.insertNew();
                        w.put("id", c.id);
                        w.put("callType", c.call.Id);
                        w.put("callDistance", c.call.CallDistance.ToString());
                        w.put("methodName", c.call.Method);
                        if (c.call is RemotingRuleMgrCall)
                            w.put("extraInfo", ((RemotingRuleMgrCall)c.call).Url);
                    }
                }
                return lst;
            }
            catch (Exception e)
            {
                throw;
            }
        }

        protected class internalCallMember
        {
            #region Member Variables

            private ICall m_call = null;
            private Swaf.BGuid.Guid m_id = new Swaf.BGuid.Guid();
            private int m_callAttempts = 0;
            private bool m_enabled = true;
            private string m_parentId = "";
            private DateTime m_nextEnableTime = DateTime.Now;
            private int m_failedCallsBeforeDisable;
            private int m_minutesBeforeReEnable;

            #endregion

            #region Constructors

            public internalCallMember(ICall callInstance, string parentId, int failedCallsBeforeDisable, int minutesBeforeReEnable)
            {
                this.call = callInstance;
                callAttempts = 0;
                m_enabled = true;
                m_parentId = parentId;
                m_failedCallsBeforeDisable = failedCallsBeforeDisable;
                m_minutesBeforeReEnable = minutesBeforeReEnable;
            }

            #endregion

            #region Properties
            public Swaf.BGuid.Guid id
            {
                get { return m_id; }
                set { m_id = value; }
            }

            public int callAttempts
            {
                get { return m_callAttempts; }
                set { Interlocked.Exchange(ref m_callAttempts, value); }
            }

            public ICall call
            {
                get { return m_call; }
                set { m_call = value; }
            }

            public bool enabled
            {
                get
                {
                    lock (this)
                    {
                        if (!m_enabled && DateTime.Now >= nextEnableTime)
                        {
                            m_enabled = true;
                        }
                    }
                    return m_enabled;
                }
                set
                {
                    lock (this)
                    {
                        m_enabled = value;
                    }
                }
            }

            public DateTime nextEnableTime
            {
                get
                {
                    lock (this)
                    {
                        return m_nextEnableTime;
                    }
                }
                set
                {
                    lock (this)
                    {
                        m_nextEnableTime = value;
                    }
                }
            }
            #endregion

            /// <summary>
            /// Tries to call the contained ICall instance.  If the call fails with a CallException
            /// the attempt counter is incremented until it reaches the max tries, then the instance
            /// is disabled for a fixed amount of time.
            /// </summary>
            /// <param name="success"></param>
            /// <param name="p"></param>
            /// <returns></returns>
            public object makeCall(ref bool success, ref Exception failException, params object[] p)
            {
                object result = null;
                try
                {
                    result = call.call(p);
                    success = true;
                    callAttempts = 0;

                    // Call worked, need to ensure failException is null to prevent getting a side effects
                    failException = null;
                }
                catch (CallException e)
                {
                    Application.currentApp.Log.println("Error calling Composite call instance for Call {0} instance {1}.  \nError was {2}", m_parentId, this.id, e.ToString());
                    success = false;
                    callAttempts++;
                    failException = e;
                }
                catch (Exception e)
                {
                    failException = e;
                }

                if (callAttempts >= m_failedCallsBeforeDisable)
                {
                    enabled = false;
                    nextEnableTime = DateTime.Now.AddMinutes(m_minutesBeforeReEnable);
                }

                return result;
            }
        }
    }
    #region Business Rules
    public class ManagementRules
    {
        public IBizObjList getRuleWorkerList(IApplication app, IRuleContext context, string ruleName)
        {
            BaseCompositeCall ruleInfo = getCompositeRuleInfo(ruleName);
            if (ruleInfo != null)
                return ruleInfo.getWorkerList();
            else
                return null;
        }
        public void addCall(IApplication app, IRuleContext context, string ruleName, ICall newCall)
        {
            BaseCompositeCall ruleInfo = getCompositeRuleInfo(ruleName);
            if (ruleInfo != null)
                ruleInfo.addCall(newCall);
            else
                throw new Swaf.BaseException(string.Format("Rule {0} is not defined"));

        }
        /// <summary>
        /// Dynamically defines a remote rule definition.
        /// </summary>
        /// <param name="app"></param>
        /// <param name="context"></param>
        /// <param name="ruleName"></param>
        /// <param name="ruleDef">IBizObj instance of either CallInfo or CompositeCallWorker</param>
        public void addRemoteRuleCall(IApplication app, IRuleContext context, string ruleName, IBizObj ruleDef)
        {
            BaseCompositeCall ruleInfo = getCompositeRuleInfo(ruleName);
            if (ruleInfo != null)
            {
                ICall rmtRule = new RemotingRuleMgrCall(ruleDef);
                ruleInfo.addCall(rmtRule);
            }
            else
                throw new Swaf.BaseException(string.Format("Rule {0} is not defined"));
        }
        public void removeCall(IApplication app, IRuleContext context, string ruleName, Swaf.BGuid.Guid id)
        {
            BaseCompositeCall ruleInfo = getCompositeRuleInfo(ruleName);
            if (ruleInfo != null)
                ruleInfo.removeCall(id);
            else
                throw new Swaf.BaseException(string.Format("Rule {0} is not defined"));

        }
        private BaseCompositeCall getCompositeRuleInfo(string ruleName)
        {
            BasicRuleInfo r = (BasicRuleInfo)((IRuleManagerEx)Application.currentApp.ruleMgr).getRuleInfo(ruleName);
            if (r.CallInfo is BaseCompositeCall)
            {
                return (BaseCompositeCall)r.CallInfo;
            }
            return null;
        }
    }
    #endregion

}
